# 在定时器配置和使能完成，并使能定时器局部中断和全局中断后，定时器根据设定的初始值进行自检，当自减为0时，硬件将ESTAT寄存器中IS字段的第11位（定时器中断位）设置为1。
# ESTAT寄存器用于记录例外的状态信息。
# 其中，IS字段中的13个位用于记录LoongArch架构支持的13个中断的状态，若某个中断对应的位被硬件设置为1，则表示产生了该中断。
# 当定时器产生中断，硬件将ESTAT寄存器的IS字段中的第11位设置为1后，若ECFG寄存器的LIE字段中的第11位为1，且CRMD寄存器中的IE字段同时为1，则硬件会进行如下操作：
# 
# 1) 首先将CRMD寄存器中的IE字段保存在PRMD寄存器的PIE字段中，并将CRMD寄存器中的IE字段设置为0，表示禁止中断。
# 然后，将CRMD寄存器的PLV字段保存在PRMD寄存器的PPLV字段中，并将CRMD寄存器中的PLV字段设置为0。
# PRMD寄存器在中断产生时，用于保存CRMD寄存器中的IE和PLV字段;在中断返回时，将这2个字段的值恢复到CRMD寄存器中。
# 
# 2) 将产生中断时下一条运行指令的地址保存到ERA寄存器中。
# 当时钟中断初始化完成，并开启系统中断后，系统进入死循环（第2章内容，非本代码），等待时钟中断产生。
# 当时钟中断产生后，ERA寄存器中保存的是死循环中的指令的地址。
# 
# 3) 跳转到EENTRY寄存器中存放的中断处理函数exception_handler的入口地址处。

#define CSR_PRMD 0x1
#define CSR_ESTAT 0x5
#define CSR_ERA 0x6
#define CSR_PGD 0x1b
#define CSR_SAVE0 0x30
#define CSR_SAVE1 0x31
#define CSR_TLBRSAVE 0x8b
#define STACK_SIZE 0xf8
#define A0_OFFSET 0x10
#define A7_OFFSET 0x48
#define ERA_OFFSET 0xf0
#define NR_exe 6

# LoongArch架构中共有32个通用寄存器，中断现场由除r0和sp寄存器之外的30个寄存器的值组成。
# 为了提升代码的简洁度，将30个寄存器的保存与恢复定义为store_load_regs宏，将30个寄存器组成的中断现场保存到上一行预留的内核初始化栈上
.macro store_load_regs cmd
	\cmd $ra, $sp, 0x0
	\cmd $tp, $sp, 0x8
	\cmd $a0, $sp, 0x10
	\cmd $a1, $sp, 0x18
	\cmd $a2, $sp, 0x20
	\cmd $a3, $sp, 0x28
	\cmd $a4, $sp, 0x30
	\cmd $a5, $sp, 0x38
	\cmd $a6, $sp, 0x40
	\cmd $a7, $sp, 0x48
	\cmd $t0, $sp, 0x50
	\cmd $t1, $sp, 0x58
	\cmd $t2, $sp, 0x60
	\cmd $t3, $sp, 0x68
	\cmd $t4, $sp, 0x70
	\cmd $t5, $sp, 0x78
	\cmd $t6, $sp, 0x80
	\cmd $t7, $sp, 0x88
	\cmd $t8, $sp, 0x90
	\cmd $r21, $sp,0x98
	\cmd $fp, $sp, 0xa0
	\cmd $s0, $sp, 0xa8
	\cmd $s1, $sp, 0xb0
	\cmd $s2, $sp, 0xb8
	\cmd $s3, $sp, 0xc0
	\cmd $s4, $sp, 0xc8
	\cmd $s5, $sp, 0xd0
	\cmd $s6, $sp, 0xd8
	\cmd $s7, $sp, 0xe0
	\cmd $s8, $sp, 0xe8
.endm

	.globl exception_handler
	.globl tlb_handler
	.globl fork_ret
fork_ret:
	addi.d $sp, $sp, -STACK_SIZE
	st.d $r0, $sp, A0_OFFSET
	b user_exception_ret

exe_ret:
	beqz $a0, user_exception_ret
	st.d $r0, $sp, ERA_OFFSET
	csrwr $a0, CSR_SAVE0
	b user_exception_ret

syscall:
	ld.d $t0, $sp, ERA_OFFSET
	addi.d $t0, $t0, 4
	st.d $t0, $sp, ERA_OFFSET
	la $t0, syscalls
	alsl.d $a7, $a7, $t0, 3
	ld.d $t0, $a7, 0 
	jirl $ra, $t0, 0
	ld.d $t0, $sp, A7_OFFSET
	ori $t1, $r0, NR_exe
	beq $t0, $t1, exe_ret
	st.d $a0, $sp, A0_OFFSET
	b user_exception_ret

exception_handler:
	csrwr $t0, CSR_SAVE1
	csrrd $t0, CSR_PRMD
	andi $t0, $t0, 0x3
	beqz $t0, kernel_exception
	b user_exception

tlb_handler:
	csrwr $t0, CSR_TLBRSAVE
	csrrd $t0, CSR_PGD
	lddir $t0, $t0, 1
	ldpte $t0, 0
	ldpte $t0, 1
	tlbfill
	csrrd $t0, CSR_TLBRSAVE
	ertn

user_exception:
	csrrd $t0, CSR_SAVE1
	csrwr $sp, CSR_SAVE0
	addi.d $sp, $sp, -STACK_SIZE
	store_load_regs st.d
	csrrd $t0, CSR_ERA
	st.d $t0, $sp, 0xf0
	csrrd $t0, CSR_ESTAT
	srli.d $t0, $t0, 16
	andi $t0, $t0, 0x3f
	ori $t1, $r0, 0xb
	beq $t0, $t1, syscall
	bl do_exception                 

user_exception_ret:
	bl do_signal
	ori $t0, $r0, 0x7
	csrwr $t0, CSR_PRMD
	ld.d $t0, $sp, 0xf0
	csrwr $t0, CSR_ERA
	store_load_regs ld.d
	addi.d $sp, $sp, STACK_SIZE
	csrwr $sp, CSR_SAVE0
	ertn


# 当 timer_interrupt 函数完成时钟中断处理之后，会返回到exception_handler函数中的调用中断处理函数 do_exception 的下一条指令。
# 将中断现场恢复之后，使用ertn汇编指令进行中断返回。 硬件会进行如下操作：
# 1) 将PRMD寄存器中的PPLV和PIE字段的值分别恢复到 CRMD 寄存器的PLV和IE字段中。
# 2) 将ERA寄存器中保存的指令地址加载到PC寄存器中。
kernel_exception:
	csrrd $t0, CSR_SAVE1
	addi.d $sp, $sp, -0xf0  # 将sp寄存器的值减去一个值，即在内核初始化栈上预留空间
	store_load_regs st.d
	bl do_exception         # 中断现场保存后，调用do_exception函数进行中断处理。
	store_load_regs ld.d
	addi.d $sp, $sp, 0xf0
	ertn
